﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using nRoute.Components.Composition;

namespace Orktane.ScriptActionModel
{
    public static class ScriptActionsManager
    {
        private const string SPACE = " ";
        private readonly static char[] START_TRIMCHARS = new char[] { ' ', Environment.NewLine.ToCharArray()[0] };

        private static readonly Object _lock = new Object();
        private static readonly ObservableCollection<ScriptActionInfo> _scriptActions;
        private static readonly ReadOnlyObservableCollection<ScriptActionInfo> _scriptActionsWrapper;

        static ScriptActionsManager()
		{
			// we get our collections
            _scriptActions = new ObservableCollection<ScriptActionInfo>();
            _scriptActionsWrapper = new ReadOnlyObservableCollection<ScriptActionInfo>(_scriptActions);
            Initialize();
		}

#region Properties
		
		public static ReadOnlyObservableCollection<ScriptActionInfo> ScriptActions
		{
			get { return _scriptActionsWrapper; }	
		}
		
#endregion
        
#region Initialize Related

        private static void Initialize()
        {
            // this is for future scripts
            Resource<IScriptAction>.Catalog.CollectionChanged += new NotifyCollectionChangedEventHandler(ActionScriptsCollectionChanged);

            // we also get all the currently listed scripts
            RegisterAllScriptActions();
        }

#endregion

#region Public Methods

        public static bool CanExecuteScript(string script)
        {
            string _scriptArguments;
            return (ResolveScriptInfo(script, out _scriptArguments) != null);
        }

        public static IScriptAction ResolveScriptAction(string script)
        {
            // we get the locator and arguments
            Arguments _arguments;
            ScriptActionInfo _scriptActionInfo;

            // get the locator
            var _locator = ResolveScriptLocator(script, out _scriptActionInfo, out _arguments);
            if (_locator == null) return null;

            // and return the script instance
            return _locator.GetResourceInstance() as IScriptAction;
        }
        
        public static IResourceLocator ResolveScriptLocator(string script, out ScriptActionInfo scriptActionInfo, out Arguments scriptArguments)
        {
            // basic check
            if (string.IsNullOrEmpty(script)) throw new ArgumentNullException("script");

            // by default
            scriptArguments = null;

            // we get our script
            string _scriptArguments;
            scriptActionInfo = ResolveScriptInfo(script, out _scriptArguments);
            if (scriptActionInfo == null) return null;

            // we get the meta
            var _locator = ResourceLocator.GetResourceLocator<IScriptAction>(scriptActionInfo.ScriptActionMeta.Prefix);
            if (_locator == null) return null;

            // and we get the arguments and return the locator
            if (!string.IsNullOrEmpty(_scriptArguments)) scriptArguments = new Arguments(_scriptArguments);
            return _locator;
        }

#endregion

#region Event Handlers

        private static void ActionScriptsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            lock(_lock)
            {
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    // we get the locator
                    var _locator = e.NewItems[0] as IResourceLocator;
                    RegisterScriptAction(_locator);

                }
                else if (e.Action == NotifyCollectionChangedAction.Remove)
                {
                    // we get the locator
                    var _locator = e.OldItems[0] as IResourceLocator;
                    UnregisterScriptAction(_locator);
                }
            }
        }

#endregion

#region Helper

        private static ScriptActionInfo ResolveScriptInfo(string script, out string scriptArguments)
        {
            // by default
            scriptArguments = null;

            // basic checks
            if (string.IsNullOrEmpty(script)) throw new ArgumentNullException("script");
            if (script.Trim().Length == 0) throw new ArgumentNullException("script");

            // we get the prefix
            string _trimmedScript = script.TrimStart(START_TRIMCHARS);
            var _firstSpaceIndex = _trimmedScript.IndexOf(SPACE);

            // if there is no first space index, then whole word should be a prefix
            string _prefix = (_firstSpaceIndex == -1) ? _trimmedScript.Trim() :
                _trimmedScript.Substring(0, _firstSpaceIndex).Trim();

            // we get our script
            var _scriptAction = _scriptActions.FirstOrDefault((sa) =>
                sa.ScriptActionMeta.Prefix.Equals(_prefix, StringComparison.InvariantCultureIgnoreCase));
            if (_scriptAction == null) return null;

            // and return
            if (_firstSpaceIndex != -1) scriptArguments = _trimmedScript.Substring(_firstSpaceIndex + 1);
            return _scriptAction;
        }

        private static void RegisterAllScriptActions()
        {
            lock (_lock)
            {
                // and we also process all applications already in the collection
                var _scriptActionLocators = Resource<IScriptAction>.Catalog;
                foreach (var _scriptActionLocator in _scriptActionLocators)
                {
                    RegisterScriptAction(_scriptActionLocator);
                }
            }
        }

        private static void RegisterScriptAction(IResourceLocator locator)
        {
            // basic check
            if (locator == null) return;

            // we get the meta
            var _scriptActionMeta = locator.ResourceMeta as ScriptActionMeta;
            if (_scriptActionMeta == null) return;

            // and register
            _scriptActions.Add(new ScriptActionInfo(_scriptActionMeta.ImplementationType.GUID, _scriptActionMeta));
        }

        private static void UnregisterScriptAction(IResourceLocator locator)
        {
            // basic check
            if (locator == null) return;

            // we get the meta and the id
            var _scriptActionMeta = locator.ResourceMeta as ScriptActionMeta;
            if (_scriptActionMeta == null) return;
            var _scriptActionId = _scriptActionMeta.ImplementationType.GUID;

            // we get the meta
            var _scriptActionInfo = _scriptActions.FirstOrDefault((sa) => sa.Id.Equals(_scriptActionId));
            if (_scriptActionInfo != null) _scriptActions.Remove(_scriptActionInfo);
        }

#endregion

    }
}
